﻿using System.Collections.Generic;
using Mono.Cecil;
using System;

namespace CILADA.Engine
{
    public class TypeInfo
    {
        #region Fields
        private string _name;
        private string _namespace;
        private string _fullName;
        private string _container = String.Empty;
        private bool _isNestedType;
        private List<TypeDependency> _dependsOn;

        private bool _createdWithType = false;
        public bool? _isFinal = null;
        private bool? _isAbstract = null;
        private bool? _isInterface = null;
        private TypeVisibility? _typeVisibility = null;
        private TypeClassification? _typeClass = null;
        #endregion

        #region Properties
        public string Name { get { return _name; } }
        public string Namespace { get { return _namespace; } }
        public string FullName { get { return _fullName; } }
        public string Container { get { return _container; } }
        public bool IsNestedType { get { return _isNestedType; } }
        public List<TypeDependency> DependsOn { get { return _dependsOn; } set { _dependsOn = value; } }

        public bool CreatedWithType { get { return _createdWithType; } set { _createdWithType = value; } }
        public bool? IsFinal { get { return _isFinal; } }
        public bool? IsAbstract
        {
            get { return (_isAbstract.HasValue) ? _isAbstract.Value : false; }
        }
        public bool? IsInterface
        {
            get { return (_isInterface.HasValue) ? _isInterface.Value : false; }
        }
        public TypeVisibility? Visibility { get { return _typeVisibility; } }
        public TypeClassification? Classification
        {
            // default to class if none set.
            get
            {
                return (_typeClass.HasValue) ? _typeClass : TypeClassification.Class;
            }
        }
        #endregion

        #region Constructors
        public TypeInfo(string fullName)
        {
            _fullName = fullName;
            _isNestedType = FindIfIsNestedType();
            _name = GetName();
            _namespace = GetNamespace();
            _dependsOn = new List<TypeDependency>();
        }

        public TypeInfo(string fullName, string container) : this(fullName)
        {
            _container = container;
        }

        public TypeInfo(string fullName, TypeDefinition typeDef)
            : this(fullName)
        {
            // get the needed type information from the definition
            _typeClass = TypeUtilities.GetTypeClassification(typeDef);
            _isFinal = typeDef.IsSealed;
            _isAbstract = typeDef.IsAbstract;
            _isInterface = typeDef.IsInterface;
            _typeVisibility = TypeUtilities.GetVisibilityLevel(typeDef);
            _createdWithType = true;
        }

        public TypeInfo(string fullName, string container, TypeDefinition typeDef)
            : this(fullName, typeDef)
        {
            _container = container;
        }
        #endregion

        #region Instance Methods
        public void AddDependency(TypeDependency dependency)
        {
            if (null == _dependsOn)
            {
                _dependsOn = new List<TypeDependency>();
            }

            // if the dependency isnt there yet, add it.
            if (!_dependsOn.Contains(dependency))
            {
                _dependsOn.Add(dependency);
            }
        }

        private bool FindIfIsNestedType()
        {
            // if the full name has a / in it, it is a nested type.
            return (_fullName.LastIndexOf("/") > -1);
        }

        private string GetName()
        {
            /*
             * Look for . and / 
             */
            int ixSlash = _fullName.LastIndexOf("/");
            if (ixSlash > -1)
            {
                return _fullName.Substring(ixSlash + 1);
            }
            else
            {
                int ixDot = _fullName.LastIndexOf(".");
                if (ixDot > -1)
                {
                    return _fullName.Substring(ixDot + 1);
                }
                else
                {
                    return _fullName;
                }
            }
        }

        private string GetNamespace()
        {
            /*
             * Look for . and / 
             */
            // TODO Review this code. Are there any missed cases? What to do if the name doesnt contain a slash or .?
            if (_fullName.Contains(".") || _fullName.Contains("/"))
            {
                try
                {
                    int ixSlash = _fullName.LastIndexOf("/");
                    if (ixSlash > -1)
                    {
                        return _fullName.Substring(0, ixSlash);
                    }
                    else
                    {
                        int ixDot = _fullName.LastIndexOf(".");
                        return _fullName.Substring(0, ixDot);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error getting namespace for " + _fullName + ". " + e.Message);
                    return _fullName;
                }
            }
            else
            {
                // the namespace is something unusual like <PrivateImplementationDetails>{09B08C78-8842-4FB5-9C1C-D468A267C0C8}
                // so return the empty string. Then the namespace and name will be able to be combined back to the original?. - Maybe? // TODO: Check this
                return "";
            }
        }

        public bool HasDependencyOnType(string typeName)
        {
            foreach (TypeDependency td in this.DependsOn)
            {
                if (td.TypeInfo.FullName.Equals(typeName))
                {
                    return true;
                }
            }
            return false;
        }

        public void MergeWithTypeInfo(TypeInfo typeInfo)
        {
            UpdateWithTypeInfo(typeInfo);

            // add the dependencies
            if (0 == typeInfo.DependsOn.Count) return;

            foreach (TypeDependency td in typeInfo.DependsOn)
            {
                if (!this.DependsOn.Contains(td))
                {
                    this.DependsOn.Add(td);
                }
            }
        }

        public void UpdateWithTypeInfo(TypeInfo typeInfo)
        {
            if (!this.FullName.Equals(typeInfo.FullName)) return;
            if (this.CreatedWithType && !typeInfo.CreatedWithType) return;

            this._typeClass = typeInfo.Classification;
            if(typeInfo.IsFinal != null) this._isFinal = typeInfo.IsFinal;
            if (typeInfo.IsAbstract != null) this._isAbstract = typeInfo.IsAbstract;
            if (typeInfo.IsInterface != null) this._isInterface = typeInfo.IsInterface;
            if (typeInfo.Visibility != null) this._typeVisibility = typeInfo.Visibility;
            this._container = typeInfo.Container;
            this._createdWithType = typeInfo.CreatedWithType;
        }

        #region Equals, GetHashCode, and ToString overrides
        public override bool Equals(object obj)
        {
            if (null == obj || !(obj is TypeInfo))
            {
                return false;
            }
            TypeInfo other = (TypeInfo)obj;
            return this.FullName.Equals(other.FullName);
        }

        public override int GetHashCode()
        {
            return this._fullName.GetHashCode();
        }

        public override string ToString()
        {
            return this.Namespace + " " + this.Name + " depends on " + ((null == _dependsOn) ? "an unknown number of " : _dependsOn.Count.ToString()) + " types.";
        }
        #endregion
        #endregion
    }
}
